﻿using System;
using System.Globalization;
using System.Windows.Data;
using System.Windows.Media;

namespace WPFLoaders
{
    /// <summary>Converter that takes a color scale ratio 0-1 and converts it to a solid color on the scale</summary>
    [ValueConversion(typeof(double), typeof(SolidColorBrush))]
    public class ColorScaleConverter : IValueConverter
    {
        static ColorScaleConverter()
        {
            Instance = new ColorScaleConverter();
        }

        public static ColorScaleConverter Instance { get; private set; }

        public ColorScaleConverterOutput OutputType = ColorScaleConverterOutput.SolidColorBrush;

        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (!(value is double))
                throw new ArgumentException(string.Format("Invalid input type '{0}' expected 'double'", GetType(value)), "value");

            var color = (double) value;

            if (OutputType == ColorScaleConverterOutput.Color)
                return ToColorScale(color);

            return new SolidColorBrush(ToColorScale(color));
        }

        private string GetType(object value)
        {
            return value == null ? "null" : value.GetType().Name;
        }

        private Color ToColorScale(double color)
        {
            return HSL2RGB(color, 0.5, 0.5);
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotSupportedException("Only conversion from ratio to color is supported");
        }

        #region Rainbow color method

        // Kudos to: http://www.geekymonkey.com/Programming/CSharp/RGB2HSL_HSL2RGB.htm

            // 
      // Returns a Color (RGB struct) in range of 0-255
        /// <summary>
        /// Given H,S,L in range of 0-1 returns a Color (RGB struct) in range of 0-255
        /// </summary>
        /// <param name="h"></param>
        /// <param name="sl"></param>
        /// <param name="l"></param>
        /// <returns></returns>
        public static Color HSL2RGB(double h, double sl, double l)
        {
            double v;
            double r, g, b;

            r = l; // default to gray
            g = l;
            b = l;
            v = (l <= 0.5) ? (l*(1.0 + sl)) : (l + sl - l*sl);
            if (v > 0)
            {
                double m;
                double sv;
                int sextant;
                double fract, vsf, mid1, mid2;

                m = l + l - v;
                sv = (v - m)/v;
                h *= 6.0;
                sextant = (int) h;
                fract = h - sextant;
                vsf = v*sv*fract;
                mid1 = m + vsf;
                mid2 = v - vsf;
                switch (sextant)
                {
                    case 0:
                        r = v;
                        g = mid1;
                        b = m;
                        break;
                    case 1:
                        r = mid2;
                        g = v;
                        b = m;
                        break;
                    case 2:
                        r = m;
                        g = v;
                        b = mid1;
                        break;
                    case 3:
                        r = m;
                        g = mid2;
                        b = v;
                        break;
                    case 4:
                        r = mid1;
                        g = m;
                        b = v;
                        break;
                    case 5:
                        r = v;
                        g = m;
                        b = mid2;
                        break;
                }
            }

            return Color.FromRgb(
                System.Convert.ToByte(r*255.0f),
                System.Convert.ToByte(g*255.0f),
                System.Convert.ToByte(b*255.0f)
                );
        }


        /// <summary>Given a color returns HSL in range 0-1</summary>
        public static void ConvertToHSL(Color rgb, out double h, out double s, out double l)
        {
            double r = rgb.R/255.0;
            double g = rgb.G/255.0;
            double b = rgb.B/255.0;
            double v;
            double m;
            double vm;
            double r2, g2, b2;

            h = 0; // default to black
            s = 0;
            l = 0;
            v = Math.Max(r, g);
            v = Math.Max(v, b);
            m = Math.Min(r, g);
            m = Math.Min(m, b);
            l = (m + v)/2.0;
            if (l <= 0.0)
            {
                return;
            }
            vm = v - m;
            s = vm;
            if (s > 0.0)
            {
                s /= (l <= 0.5) ? (v + m) : (2.0 - v - m);
            }
            else
            {
                return;
            }
            r2 = (v - r)/vm;
            g2 = (v - g)/vm;
            b2 = (v - b)/vm;
            if (r == v)
            {
                h = (g == m ? 5.0 + b2 : 1.0 - g2);
            }
            else if (g == v)
            {
                h = (b == m ? 1.0 + r2 : 3.0 - b2);
            }
            else
            {
                h = (r == m ? 3.0 + g2 : 5.0 - r2);
            }
            h /= 6.0;
        }

        #endregion
    }

    /// <summary>Type to output from the <see cref="ColorScaleConverter"/></summary>
    public enum ColorScaleConverterOutput
    {
        Color,
        SolidColorBrush
    }
}
